/*
    Copyright (c) 2003-2016 Pentair Technical Products, Inc.
    All rights reserved.
    Pentair Technical Products, Inc. proprietary and confidential.

    Description:
	This header file defines the interfaces
	provided by the i2c.c module.

    $Revision: 13617 $
*/

#ifndef __I2C_H__
#define __I2C_H__

/*
    I2C buffer status
*/

#define I2C_BUFFER_OFF			0
#define I2C_BUFFER_WAITING		1
#define I2C_BUFFER_NOTREADY		2
#define I2C_BUFFER_READY		3
#define I2C_BUFFER_LOCAL_ERROR		4
#define I2C_BUFFER_TRANSACTION_TIMEOUT	5
#define I2C_BUFFER_SCL_LOW_TIMEOUT	6

/*
    Return codes
*/

#define I2C_OK				0
#define I2C_NACK			-1
#define I2C_BUSY			-2
#define I2C_NOTREADY		-3
#define I2C_TIMEOUT			-4
#define I2C_ERROR			-5

/*
    I2C flag bits
*/

#define I2C_WRITE               	0x0000
#define I2C_READ                	0x0001
#define I2C_START               	0x4000
#define I2C_STOP                	0x8000
#define I2C_RSTART              	I2C_START

#define I2C_MONLY1			0x0000
#define I2C_MONLY2			0x0100
#define I2C_MONLY_MASK			0x3F00
#define I2C_MONLY_SHIFT			8
#define MO_CHANNEL_ADDRESS(mo_ch, addr)	((addr) | \
	((mo_ch << I2C_MONLY_SHIFT) & I2C_MONLY_MASK))


/* I2C address flag bits */
#define I2C_ADDR_GCALL			0x0001

/* I2C broadcast message flag */
#define I2C_BCAST			0x80

#ifdef CFG_DEBUG_ENABLE
#define I2C_DBG_STR(desc, x)	\
    if (CFG_I2C_DEBUG_MASK & (1 << desc->channel)) hal_dbg_str(x)
#define I2C_DBG_HEX(desc, x)	\
    if (CFG_I2C_DEBUG_MASK & (1 << desc->channel)) hal_dbg_hex(x)
#define I2C_DBG_FLUSH		hal_dbg_flush()
#else
#define I2C_DBG_STR(desc, x)
#define I2C_DBG_HEX(desc, x)
#define I2C_DBG_FLUSH
#endif

#ifndef CFG_I2C_DEBUG_MASK
#define CFG_I2C_DEBUG_MASK		0
#endif

#ifndef CFG_UNIT_TEST_I2C_TIMEOUT_MASK
#define CFG_UNIT_TEST_I2C_TIMEOUT_MASK	0xFFFF
#endif

/*
    Disable IPMB channels in the bootloader to save space.
 */
#ifdef __BOOTLOADER__
#define	_I2C_EN(__type)	((__type) != I2C_TYPE_IPMB)
#else
#define	_I2C_EN(__type)	1
#endif

/* The following macro is used to declare an I2C channel descriptor */
#define DECLARE_I2C_DESC(chan, _ops, ring_size, _rom, _ram, _rate,	\
	_type, _timeout, ...)						\
    DECLARE_RING(i2c_ring_##chan, _I2C_EN(_type) ? ring_size : 0);	\
    i2c_ram_t i2c_ram_##chan =						\
    {									\
	.buf_status =	I2C_BUFFER_OFF,					\
    };									\
    i2c_desc_t i2c_desc_##chan SECTION(".i2c_desc."#chan) =		\
    {									\
	.channel =	(chan),						\
	.type =		(_type),					\
	.rate =		(_rate),					\
	.ops =		_I2C_EN(_type) ? (_ops) : NULL,			\
	.ring =		&i2c_ring_##chan,				\
	.priv_rom =	(_rom),						\
	.priv_ram =	(_ram),						\
	.ram =		&i2c_ram_##chan,				\
	.timeout =	(_timeout),					\
	__VA_ARGS__							\
    };

#if 0
/* Dynamic part of the I2C descriptor (stored in RAM) */
typedef struct {
    volatile unsigned char  buf_status;
    time_t		    buf_timer;
    unsigned char	    in_local_segment_check;
    int			    last_error;
    time_t		    last;
} i2c_ram_t;

/* I2C driver API: operations */
struct i2c_desc_s;

typedef struct {
    /* Initialize the I2C controller */
    int (*init)(struct i2c_desc_s *);
    /* Set address(es) */
    char (*set_addr)(struct i2c_desc_s *, unsigned char, unsigned char);
    /* Send data */
    int (*send)(struct i2c_desc_s *, unsigned short, unsigned char *, int);
    /* Receive data */
    int (*receive)(struct i2c_desc_s *, unsigned short, unsigned char *, int);
    /* Check if a given I2C channel is idle */
    int (*is_idle)(struct i2c_desc_s *);
    void (*enable)(struct i2c_desc_s *);
    void (*disable)(struct i2c_desc_s *);
} i2c_ops_t;

/* Static I2C descriptor structure (stored in ROM) */
struct i2c_desc_s {
    unsigned char channel;	/* Channel number */
    unsigned char type;		/* Channel type */
#define I2C_TYPE_IPMB	0x00	/* IPMB channel */
#define I2C_TYPE_MONLY	0x01	/* Master-only channel */
    unsigned int rate;		/* Master clock rate */
    i2c_ops_t *ops;		/* Driver operations */
    ring_t *ring;		/* Receive ring */
    i2c_ram_t *ram;		/* In-RAM data */
    void *priv_rom;		/* Driver specific ROM structure */
    void *priv_ram;		/* Driver specific RAM data */
    time_t timeout;		/* Operation timeout */
    pin_t buf_en_pin;		/* Buffer enable pin */
    pin_t buf_rdy_pin;		/* Buffer ready pin */
};

typedef struct i2c_desc_s i2c_desc_t;

/* External API */

#define I2C_DESC_CNT (&i2c_desc_table_end - &i2c_desc_table_start)

#define I2C_RING(chan)	(i2c_desc_table[(chan)].ring)

extern void i2c_init_all(void);
extern void i2c_buffer_attach(unsigned char channel);
extern unsigned char i2c_buffer_status(unsigned char channel);
extern void i2c_buffer_detach(unsigned char channel);
extern void i2c_buffer_detach_nowait(i2c_desc_t *desc);
extern void i2c_reset(unsigned char channel);
extern ring_t *i2c_get_ring(unsigned char channel);
extern char i2c_is_local_segment_functioning(unsigned char channel);

/** returns status of the last master operation on channel.
    @param	channel	i2c channel to get info on.
    @returns		i2c error code (onr of I2C_xxx)
*/
int i2c_get_last_error(unsigned char channel);

/** The last master-only I2C error. This macro is defined for backward
    compatibility only. All new code should use i2c_get_last_error() */
#define i2c_masteronly_last_error   \
	i2c_get_last_error(i2c_masteronly_channel_from_index(0))

#define I2C_BAD_CHANNEL            0xFF

/** get channel number for MO channel number.
    @param	mo_ch	MO local channel number.
    @returns		MO global channel number.
*/
unsigned char i2c_masteronly_channel_from_index(unsigned char mo_ch);

/** Decode channel number from address bits. Used to support multiple
    master-only buses.
    @param	addr	MO address with channel encoded in high bits.
    @returns		MO global channel number.
*/
static inline unsigned char i2c_masteronly_channel_from_address(
       unsigned int addr)
{
    return i2c_masteronly_channel_from_index(
		(I2C_MONLY_MASK & addr) >> I2C_MONLY_SHIFT);
}


/*
    I2C driver API: gateway functions
 */
extern i2c_desc_t *i2c_desc_table;
extern i2c_desc_t i2c_desc_table_start;
extern i2c_desc_t i2c_desc_table_end;

static inline int i2c_init(unsigned char channel)
{
    int ret = -1;

    i2c_desc_t *desc = i2c_desc_table + channel;
    if (desc->ops->init) {
	ret = desc->ops->init(desc);
    }
    return ret;
}

static inline char i2c_set_addr(unsigned char channel, unsigned char addr1,
    unsigned char addr2)
{
    char ret = -1;

    i2c_desc_t *desc = i2c_desc_table + channel;
    if (desc->ops->set_addr) {
	ret = desc->ops->set_addr(desc, addr1, addr2);
    }
    return ret;
}

static inline int i2c_send(unsigned char channel, unsigned short i2c_addr,
	unsigned char *buf, int len)
{
    int ret = I2C_ERROR;

    i2c_desc_t *desc = i2c_desc_table + channel;
    if (desc->ops->send) {
	if (desc->type != I2C_TYPE_IPMB
		|| desc->buf_en_pin == PIN_INVALID
		|| desc->ram->buf_status == I2C_BUFFER_READY
		|| desc->ram->in_local_segment_check) {
	    ret = desc->ops->send(desc, i2c_addr, buf, len);
	} else {
	    ret = I2C_NOTREADY;
	}
    }
    if (ret < 0) {
	desc->ram->last_error = ret;
    } else {
	desc->ram->last_error = I2C_OK;
    }
    return ret;
}

static inline int i2c_receive(unsigned char channel, unsigned short i2c_addr,
	unsigned char *buf, int len)
{
    int ret = I2C_ERROR;

    i2c_desc_t *desc = i2c_desc_table + channel;
    if (desc->ops->receive) {
	ret = desc->ops->receive(desc, i2c_addr, buf, len);
    }
    if (ret < 0) {
	desc->ram->last_error = ret;
    } else {
	desc->ram->last_error = I2C_OK;
    }
    return ret;
}

static inline int i2c_is_idle(unsigned char channel)
{
    int ret = -1;

    i2c_desc_t *desc = i2c_desc_table + channel;
    if (desc->ops->is_idle) {
	ret = desc->ops->is_idle(desc);
    }
    return ret;
}

static inline void i2c_disable(unsigned char channel)
{
    time_t timer;
    i2c_desc_t *desc = i2c_desc_table + channel;
    if (desc->ops && desc->ops->disable) {
	desc->ops->disable(desc);
	timer_set(timer);
	while(!i2c_is_idle(channel) &&
	      !timer_is_expired(timer, desc->timeout));

    }
}

static inline void i2c_enable(unsigned char channel)
{
    i2c_desc_t *desc = i2c_desc_table + channel;
    if (desc->ops && desc->ops->enable) {
	desc->ops->enable(desc);
    }
}

/* Wrapper for all master-only buses */
static inline int i2c_io_ext(unsigned char channel,
	unsigned short addr, unsigned char *buf, unsigned char len)
{
    char ret;

    if ((addr & I2C_READ)) {
	ret = i2c_receive(channel, addr, buf, len);
    } else {
	ret = i2c_send(channel, addr, buf, len);
    }
    return (ret > 0) ? ret : 0;
}

#if defined(CFG_I2C_MASTERONLY_CHANNEL) || defined(CFG_I2C_MO_CHANNELS)
/* Master-only I2C wrapper */
static inline int i2c_io(unsigned short addr, unsigned char *buf,
	unsigned char len)
{
    unsigned char channel = i2c_masteronly_channel_from_address(addr);
    return i2c_io_ext(channel, addr, buf, len);
}
#endif /* CFG_I2C_MASTERONLY_CHANNEL */

#endif

#endif /* __I2C_H__ */
